<!DOCTYPE html>
<!--
Copyright 2016 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/utils.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/value/diagnostics/diagnostic_map.html">
<link rel="import" href="/tracing/value/histogram.html">

<script>
'use strict';

tr.exportTo('tr.metrics', function() {
  const MEMORY_INFRA_TRACING_CATEGORY = 'disabled-by-default-memory-infra';

  const TIME_BOUNDARIES = tr.v.HistogramBinBoundaries.createExponential(
      1e-3, 1e5, 30);

  const BYTE_BOUNDARIES = tr.v.HistogramBinBoundaries.createExponential(
      1, 1e9, 30);

  const COUNT_BOUNDARIES = tr.v.HistogramBinBoundaries.createExponential(
      1, 1e5, 30);

  // By default, we store a single value, so we only need one of the
  // statistics to keep track. We choose the average for that.
  const SUMMARY_OPTIONS = tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS;

  // Adds histograms specific to memory-infra dumps.
  function addMemoryInfraHistograms(
      histograms, model, categoryNamesToTotalEventSizes) {
    const memoryDumpCount = model.globalMemoryDumps.length;
    if (memoryDumpCount === 0) return;

    let totalOverhead = 0;
    let nonMemoryInfraThreadOverhead = 0;
    const overheadByProvider = {};
    for (const process of Object.values(model.processes)) {
      for (const thread of Object.values(process.threads)) {
        for (const slice of Object.values(thread.sliceGroup.slices)) {
          if (slice.category !== MEMORY_INFRA_TRACING_CATEGORY) continue;

          totalOverhead += slice.duration;
          if (thread.name !== 'MemoryInfra') {
            nonMemoryInfraThreadOverhead += slice.duration;
          }
          if (slice.args && slice.args['dump_provider.name']) {
            const providerName = slice.args['dump_provider.name'];
            let durationAndCount = overheadByProvider[providerName];
            if (durationAndCount === undefined) {
              overheadByProvider[providerName] = durationAndCount =
                  {duration: 0, count: 0};
            }
            durationAndCount.duration += slice.duration;
            durationAndCount.count++;
          }
        }
      }
    }

    histograms.createHistogram('memory_dump_cpu_overhead',
        tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
        totalOverhead / memoryDumpCount, {
          binBoundaries: TIME_BOUNDARIES,
          description:
            'Average CPU overhead on all threads per memory-infra dump',
          summaryOptions: SUMMARY_OPTIONS,
        });

    histograms.createHistogram('nonmemory_thread_memory_dump_cpu_overhead',
        tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
        nonMemoryInfraThreadOverhead / memoryDumpCount, {
          binBoundaries: TIME_BOUNDARIES,
          description: 'Average CPU overhead on non-memory-infra threads ' +
            'per memory-infra dump',
          summaryOptions: SUMMARY_OPTIONS,
        });

    for (const [providerName, overhead] of Object.entries(overheadByProvider)) {
      histograms.createHistogram(`${providerName}_memory_dump_cpu_overhead`,
          tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,
          overhead.duration / overhead.count, {
            binBoundaries: TIME_BOUNDARIES,
            description:
              `Average CPU overhead of ${providerName} per OnMemoryDump call`,
            summaryOptions: SUMMARY_OPTIONS,
          });
    }

    const memoryInfraEventsSize =
        categoryNamesToTotalEventSizes.get(MEMORY_INFRA_TRACING_CATEGORY);
    const memoryInfraTraceBytesValue = new tr.v.Histogram(
        'total_memory_dump_size',
        tr.b.Unit.byName.sizeInBytes_smallerIsBetter, BYTE_BOUNDARIES);
    memoryInfraTraceBytesValue.description =
        'Total trace size of memory-infra dumps in bytes';
    memoryInfraTraceBytesValue.customizeSummaryOptions(SUMMARY_OPTIONS);
    memoryInfraTraceBytesValue.addSample(memoryInfraEventsSize);
    histograms.addHistogram(memoryInfraTraceBytesValue);

    const traceBytesPerDumpValue = new tr.v.Histogram(
        'memory_dump_size',
        tr.b.Unit.byName.sizeInBytes_smallerIsBetter, BYTE_BOUNDARIES);
    traceBytesPerDumpValue.description =
        'Average trace size of memory-infra dumps in bytes';
    traceBytesPerDumpValue.customizeSummaryOptions(SUMMARY_OPTIONS);
    traceBytesPerDumpValue.addSample(memoryInfraEventsSize / memoryDumpCount);
    histograms.addHistogram(traceBytesPerDumpValue);
  }

  // TODO(charliea): The metrics in this file should be renamed to have names
  // more consistent with those in the rest of the codebase
  // (e.g. 'trace_size_growth_per_second', not 'Max event size in bytes per
  // second').
  // https://github.com/catapult-project/catapult/issues/3233
  function tracingMetric(histograms, model) {
    if (!model.stats.hasEventSizesinBytes) return;

    const eventStats = model.stats.allTraceEventStatsInTimeIntervals;
    eventStats.sort((a, b) => a.timeInterval - b.timeInterval);

    const totalTraceBytes = eventStats.reduce(
        (a, b) => a + b.totalEventSizeinBytes, 0);

    // We maintain a sliding window of records [start ... end-1] where end
    // increments each time through the loop, and we move start just far enough
    // to keep the window less than 1 second wide. Note that we need to compute
    // the number of time intervals (i.e. units that timeInterval is given in)
    // in one second to know how wide the sliding window should be.
    let maxEventCountPerSec = 0;
    let maxEventBytesPerSec = 0;
    const INTERVALS_PER_SEC = Math.floor(
        1000 / model.stats.TIME_INTERVAL_SIZE_IN_MS);

    let runningEventNumPerSec = 0;
    let runningEventBytesPerSec = 0;
    let start = 0;
    let end = 0;

    while (end < eventStats.length) {
      // Slide the end marker forward. Moving the end marker from N
      // to N+1 adds eventStats[N] to the sliding window.
      runningEventNumPerSec += eventStats[end].numEvents;
      runningEventBytesPerSec += eventStats[end].totalEventSizeinBytes;
      end++;

      // Slide the start marker forward so that the time interval covered
      // by the window is less than 1 second wide.
      while ((eventStats[end - 1].timeInterval -
              eventStats[start].timeInterval) >= INTERVALS_PER_SEC) {
        runningEventNumPerSec -= eventStats[start].numEvents;
        runningEventBytesPerSec -= eventStats[start].totalEventSizeinBytes;
        start++;
      }

      // Update maximum values.
      maxEventCountPerSec = Math.max(maxEventCountPerSec,
          runningEventNumPerSec);
      maxEventBytesPerSec = Math.max(maxEventBytesPerSec,
          runningEventBytesPerSec);
    }

    const stats = model.stats.allTraceEventStats;
    const categoryNamesToTotalEventSizes = (
      stats.reduce((map, stat) => (
        map.set(stat.category,
            ((map.get(stat.category) || 0) +
                    stat.totalEventSizeinBytes))), new Map()));

    // Determine the category with the highest total event size.
    const maxCatNameAndBytes = Array.from(
        categoryNamesToTotalEventSizes.entries()).reduce(
        (a, b) => ((b[1] >= a[1]) ? b : a));
    const maxEventBytesPerCategory = maxCatNameAndBytes[1];
    const categoryWithMaxEventBytes = maxCatNameAndBytes[0];

    const maxEventCountPerSecValue = new tr.v.Histogram(
        'peak_event_rate', tr.b.Unit.byName.count_smallerIsBetter,
        COUNT_BOUNDARIES);
    maxEventCountPerSecValue.description = 'Max number of events per second';
    maxEventCountPerSecValue.customizeSummaryOptions(SUMMARY_OPTIONS);
    maxEventCountPerSecValue.addSample(maxEventCountPerSec);

    const maxEventBytesPerSecValue = new tr.v.Histogram(
        'peak_event_size_rate', tr.b.Unit.byName.sizeInBytes_smallerIsBetter,
        BYTE_BOUNDARIES);
    maxEventBytesPerSecValue.description = 'Max event size in bytes per second';
    maxEventBytesPerSecValue.customizeSummaryOptions(SUMMARY_OPTIONS);
    maxEventBytesPerSecValue.addSample(maxEventBytesPerSec);

    const totalTraceBytesValue = new tr.v.Histogram('trace_size',
        tr.b.Unit.byName.sizeInBytes_smallerIsBetter, BYTE_BOUNDARIES);
    totalTraceBytesValue.customizeSummaryOptions(SUMMARY_OPTIONS);
    totalTraceBytesValue.addSample(totalTraceBytes);

    const biggestCategory = {
      name: categoryWithMaxEventBytes,
      size_in_bytes: maxEventBytesPerCategory
    };

    totalTraceBytesValue.diagnostics.set(
        'category_with_max_event_size',
        new tr.v.d.GenericSet([biggestCategory]));
    histograms.addHistogram(totalTraceBytesValue);

    maxEventCountPerSecValue.diagnostics.set(
        'category_with_max_event_size',
        new tr.v.d.GenericSet([biggestCategory]));
    histograms.addHistogram(maxEventCountPerSecValue);

    maxEventBytesPerSecValue.diagnostics.set(
        'category_with_max_event_size',
        new tr.v.d.GenericSet([biggestCategory]));
    histograms.addHistogram(maxEventBytesPerSecValue);

    addMemoryInfraHistograms(histograms, model, categoryNamesToTotalEventSizes);
  }

  tr.metrics.MetricRegistry.register(tracingMetric);

  return {
    tracingMetric,
    // For testing only:
    MEMORY_INFRA_TRACING_CATEGORY,
  };
});
</script>
